/**
* Copyright c FOUNDER 2005 All Rights Reserved.
* DataProcessor.java
*/
package com.iori.common.db.dbctrl;

import java.util.HashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import com.sun.org.apache.regexp.internal.RE;
import com.sun.org.apache.regexp.internal.RESyntaxException;

/**
 * [クラス名]<br>
 *  データプロセッサー<br><br>
 * [機能概要]<br>
 *  XMLフォーマットのSQLファイルをパースする際のハンドラクラス。<br>
 * SqlFileLoaderで使用される。<br><br>
 * [変更履歴]<br>
 *  2003/10/16 ver1.00 新規作成 zhanjc<br><br>
 *
 * @see jp.co.pia.bety.front.common.db.dbctrl.SqlFileLoader
 *
 * @author zhanjc
 * @version 1.00
 */
public class DataProcessor extends DefaultHandler {

	/**
	 * ログ出力用のインスタンス生成。
	 * log输出的instance
	 */
	protected static Log logger = LogFactory.getLog(DataProcessor.class);

	/**
	 * sqlタグが開かれていることを示す定数。
	 */
	private final int OPENED = 1;

	/**
	 * sqlタグが閉じられていることを示す定数。
	 */
	private final int CLOSED = 2;

	/**
	 * sqlエレメントが格納されるの最上位ノードのタグ名。
	 */
	private final String LIST_TAG_NAME = "sqlList";

	/**
	 * sqlエレメントのタグ名。
	 */
	private final String SQL_TAG_NAME = "sql";

	/**
	 * sqlエレメント内のsqlIdを示す属性名称。
	 */
	private final String ATTR_NAME = "sqlId";

	/**
	 * sqlListタグが開かれているかどうかのフラグ。
	 */
	private boolean listOpened;

	/**
	 * sqlタグの開始終了を示す。
	 */
	private int state = CLOSED;

	/**
	 * 最後に読み込み成功したsqlタグのｓｑｌＩｄ。
	 */
	private String sqlId;

	/**
	 * 最後に読み込み成功したsqlタグのｓｑｌ文。
	 */
	private String sql;

	/**
	 * sqlIdをキー、sql文をバリューとして格納するためのMap。
	 */
	private HashMap map;

	/**
	 * コメント用フラグ
	 */
	private boolean commentFlag = false;

	/**
	 * コンストラクタ。<br>
	 * sqlを格納するmapを初期化を行う
	 *
	 * @param map sqlの格納先マップ
	 */
	public DataProcessor(HashMap map){
		this.map = map;
	}

	/**
	 * 要素の開始通知を受け取る。<br>
	 * 属性の取得とエラーチェックを行う。
	 *
	 * @param uri 前置修飾子にマッピングされた名前空間
	 * @param　localName 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
	 * @param　qName 前置修飾子を持つ修飾名。修飾名を使用できない場合は空文字列
	 * @param　attributes 指定された属性またはデフォルトの属性
	 *
	 * @exception SAXException SAX例外
	 */
	public void startElement (String uri, String localName,
                           String qName, Attributes attributes) throws SAXException{

		if(localName.equals(LIST_TAG_NAME)){
			listOpened = true;
		}
		// sqlListタグが無い
		else if(localName.equals(SQL_TAG_NAME) && !listOpened){
			throw new SAXException("<" + LIST_TAG_NAME + ">タグがありません");
		}
		else{
			// タグの属性を取得
			String tempId = attributes.getValue(ATTR_NAME);

			// タグ閉じ忘れエラー
			if(state == OPENED){
				throw new SAXException(ATTR_NAME + "='" + sqlId + "'のタグが閉じられていません");
			}
			// 属性エラー
			else if(tempId == null){
				// 1番最初のタグ
				if(sqlId == null){
					throw new SAXException("最初のエレメントの" + ATTR_NAME + "属性が不正です。");
				}
				else{
					throw new SAXException(ATTR_NAME + "='" + sqlId + "'の次のエレメントのsqlId属性が不正です。");
				}
			}
			// 正常
			else {
				sqlId = tempId;
				sql = "";
				state = OPENED;
			}
		}
	}

	/**
	 * 要素の終了通知を受け取る。<br>
	 * sqlのコメントとタブ文字を除去し、sqlIdをキー、sqlをバリューとして<br>
	 * mapに取得されたsql文を追加する。
	 *
	 * @param uri 前置修飾子にマッピングされた名前空間
	 * @param　localName 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
	 * @param　qName 前置修飾子を持つ修飾名。修飾名を使用できない場合は空文字列
	 *
	 * @exception SAXException SAX例外
	 */
	public void endElement(String uri, String localName, String qName) throws SAXException
	{
		try{
			if(localName.equals(SQL_TAG_NAME) && state == OPENED){
				//sql文の末に\nをつけて、sql文の最後行に「--」のフォーマットで注釈を作成して、注釈が空欄に切り替えられないバグを解決します。
				sql += "\n";
				RE r = null;

				// /*...*/コメントを除去
				int fromIndex = 0;
				int flag = sql.indexOf("/*", fromIndex);
				while(flag > -1){
					if(sql.indexOf("/*+", fromIndex) != flag){
						sql = sql.substring(0,flag) + sql.substring(sql.indexOf("*/",fromIndex)+2) ;
					}else{
						fromIndex = sql.indexOf("*/" , fromIndex)+2;
					}
					flag = sql.indexOf("/*", fromIndex);
				}

				/** Mod By Liuww Start 20070906*/
				//SQL分にコメントの部分を削除する
				sql = removeComment(sql);
				/** Mod By Liuww End 20070906*/

				// 改行をスペースに変換
				r = new RE("[\n]");
				if(r.match(sql)) {
					sql = r.subst(sql," ");
				}

				// タブをスペースに置換
				r = new RE("[\t]+");
				if(r.match(sql)) {
					sql = r.subst(sql," ");
				}

				sql = sql.trim();
				// マップにkeyとvalueを追加
				map.put(sqlId,sql);
				state = CLOSED;
			}
		}catch (RESyntaxException e) {
			logger.error(e.getMessage());
		}
	}

	/**
	 * 要素内の文字データの通知を受け取る。<br>
	 * sqlタグが開始されている場合のみsqlに取得文字列を追加する。<br>
	 * sqlのコメントは除外される。
	 *
	 * @param ch 取得された文字列
	 * @param　offset 文字配列内の開始位置
	 * @param　length 文字配列から使用される文字数
	 *
	 * @exception SAXException SAX例外
	 */
	public void characters(char[] ch, int offset,int length) throws SAXException
	{
		String str = "";
		String tempStr = "";
		if(state == OPENED){
			str = new String(ch, offset, length);
			//used in type of /*+ ... */ ,It's changed line separator to space
			tempStr = " " + str.trim() + " ";

			if(tempStr == null || tempStr.equals("")){
				/** ADD By Liuww Start 20070911*/
				//修正したら、改行符号が削除されるバグが発生する可能性になります
				if(!commentFlag){
					sql += str;
				}
				/** ADD By Liuww End 20070911*/
				return;
			}

			if (commentFlag) {
				// 先回はコメント開始の場合、改行を入れない
				str = tempStr;
			}

			// コメント開始
			if(str.indexOf("/*") >= 0){
				commentFlag = true;
			}
			// コメント終了
			if(str.indexOf("*/") >= 0){
				commentFlag = false;
			}

//			int arraySize = ch.length;
			// SAXが持つchのバッファサイズを超えたコンテンツを持つタグがある場合に、charactersが改行の前に呼ばれてしまうため、
			// バッファサイズを超えた場合には改行を挿入しない。
			// また、/* ... */ コメントには改行を入れない
//			if(offset + length >= arraySize || commentFlag){
//				sql += str;
//			}else{
//				sql += str + "\n";
//			}
			sql += str;
		}
	}

	/**
	 * 致命的な XML 構文解析エラーを報告する。<br>
	 * 終了タグの欠落を検出する。
	 *
	 * @param e 例外としてエンコードされたエラー情報
	 *
	 * @exception SAXException SAX例外
	 */
	public void fatalError(SAXParseException e) throws SAXException
	{
		String message = "";

		// 閉じるタグがないエラー
		if(state == CLOSED && sqlId == null){
			throw new SAXException("最初のエレメントが閉じられたタグで開始されています");
		}
		else if(state == CLOSED){
			throw new SAXException(ATTR_NAME + "='" + sqlId + "'の次のエレメントが閉じられたタグで開始されているか、</" + LIST_TAG_NAME + ">タグが存在しません");
		}
		throw new SAXException(message);
	}

	/*
	 * 機能説明：SQLの直接置換する使い方
	 * @Author laughsmile
	 * @MethodName:directReplace
	 * @param da
	 * @param strField : データベースの置換するフィールド。例えば：#AREA_CODE
	 * @param strValue 置換するフィールドの値。例えば：'1','2'
	 * @param strCond SQL文の条件、例えば：:CONDITION_AREA_CODE
	 * @return void
	 * @throws
	 */
	public static void directReplace(DataAccessor da, String strField , String strValue , String strCond){
		da.setParameter(strField, strValue);
		java.util.HashMap tempMap = new java.util.HashMap();
		da.setParameter(strCond,tempMap);
		tempMap = null;
	}

	/**
	 * SQL分にコメントの部分を削除する
	 *
	 * @param e
	 * sql
	 */
	private static String removeComment(String str){
		String[] sqls = str.split("\n");
		String sql = "";
		for(int i = 0 ; i < sqls.length; i++){
			String sqlTemp = sqls[i] + "\n";
			int fromIndex = 0;
			int beg = sqlTemp.indexOf("--" , fromIndex);
			while(beg >= fromIndex){
				String sqlSub = sqlTemp.substring(0 , beg);

				if(isQuoteString(sqlSub)){
					/*
					 * 「'」で囲んでいる場合、そのまま残る
					 */
					fromIndex = beg + 2;
				}else{
					/*
					 * 「'」で囲んでいない場合、「--」から行末までの文字列をコメントとして、削除する
					 */
					sqlTemp = sqlTemp.substring(0 , beg) + "\n";
					break;
				}
				beg = sqlTemp.indexOf("--" , fromIndex);
			}
			sql += sqlTemp;
		}

		return sql;
	}


	/**
	 * 文字列が「'」で囲んでいるか、どうか
	 * @param str
	 * @return true :囲んでいる false :囲んでいない
	 */
	private static boolean isQuoteString(String str){

		int num = 0;
		char sep = '\'';
		char[] subSql = str.toCharArray();
		for(char sepTemp:subSql){
			if(sep == sepTemp){
				num++ ;
			}
		}

		if (num%2 == 1){
			return true;
		}
		return false;
	}
}

